// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 2016-2018, LomboTech Co.Ltd.
 * Authors:
 *	lomboswer <lomboswer@lombotech.com>
 *
 * This program is free software; you can redistribute  it and/or modify it
 * under  the terms of  the GNU General  Public License as published by the
 * Free Software Foundation;  either version 2 of the  License, or (at your
 * option) any later version.
 */
#include <linux/iio/iio.h>
#include <linux/backlight.h>
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <linux/regulator/consumer.h>

#include "panel_lombo_simple.h"

#define ACTIVE(flag)		(((flag) & OF_GPIO_ACTIVE_LOW) ? 1 : 0)
#define INACTIVE(flag)		(((flag) & OF_GPIO_ACTIVE_LOW) ? 0 : 1)

struct panel_lombo_simple {
	struct drm_panel base;
	bool prepared;
	bool enabled;

	struct device *dev;
	struct panel_lombo_simple_desc *desc;
	struct backlight_device *backlight;
	struct regulator *supply;
	int reset_gpio;
	int power_gpio;
	int sel_gpio;
	enum of_gpio_flags reset_gpio_flag;
	enum of_gpio_flags power_gpio_flag;
	enum of_gpio_flags sel_gpio_flag;

	u32 *init_code32;
	u8 *init_code8;
	u32 init_code_num;
};

static struct i2c_client **g_clients;
static unsigned int g_client_cnt;

static inline struct panel_lombo_simple *to_panel_lombo_simple(
	struct drm_panel *panel)
{
	return container_of(panel, struct panel_lombo_simple, base);
}

int panel_lombo_simple_update_init_code(struct drm_panel *panel,
	u8 *init_code, u32 init_code_num)
{
	struct panel_lombo_simple *p = to_panel_lombo_simple(panel);

	if (!p) {
		DRM_ERROR("to_panel_lombo_simple fail\n");
		return -EINVAL;
	}

	if (p->desc->interface == LOMBO_VO_DEV_MIPI_DSI_VIDEO ||
			p->desc->interface == LOMBO_VO_DEV_MIPI_DSI_CMD) {
		devm_kfree(p->dev, p->init_code8);
		p->init_code_num = init_code_num;
		p->init_code8 = devm_kzalloc(p->dev,
				init_code_num, GFP_KERNEL);
		if (!p->init_code8) {
			DRM_ERROR("re_alloc for init_code8 fail\n");
			return -ENOMEM;
		}

		if (copy_from_user(p->init_code8, (void __user *)init_code,
				init_code_num)) {
			DRM_ERROR("update init_code8 fail\n");
			return -EINVAL;
		}
	} else {
		devm_kfree(p->dev, p->init_code32);
		p->init_code_num = init_code_num / sizeof(unsigned int);
		p->init_code32 = devm_kzalloc(p->dev,
				init_code_num, GFP_KERNEL);
		if (!p->init_code32) {
			DRM_ERROR("re_alloc for init_code32 fail\n");
			return -ENOMEM;
		}

		if (copy_from_user(p->init_code32, (void __user *)init_code,
				init_code_num)) {
			DRM_ERROR("update init_code32 fail\n");
			return -EINVAL;
		}
	}

	return 0;
}
EXPORT_SYMBOL(panel_lombo_simple_update_init_code);

int panel_lombo_simple_reconfig(struct drm_panel *panel,
	struct panel_lombo_simple_desc *desc)
{
	struct panel_lombo_simple *p = to_panel_lombo_simple(panel);

	if (!p) {
		DRM_ERROR("to_panel_lombo_simple fail\n");
		return -EINVAL;
	}

	p->desc->interface = desc->interface;
	p->desc->modes = desc->modes;
	p->desc->flag = desc->flag;
	p->desc->format = desc->format;

	if (desc->interface == LOMBO_VO_DEV_MIPI_DSI_VIDEO ||
			desc->interface == LOMBO_VO_DEV_MIPI_DSI_CMD) {
		p->desc->lanes = desc->lanes;
		p->desc->dsi_fmt = desc->dsi_fmt;
	} else if (desc->interface >= LOMBO_VO_DEV_CPU_18BIT_RGB666 &&
			desc->interface <= LOMBO_VO_DEV_CPU_8BIT_RGB888) {
		p->desc->cmd_align = desc->cmd_align;
	}

	return 0;
}
EXPORT_SYMBOL(panel_lombo_simple_reconfig);

bool panel_lombo_simple_config_change(struct drm_panel *panel,
	struct panel_lombo_simple_desc *desc)
{
	struct panel_lombo_simple *p = to_panel_lombo_simple(panel);
	struct panel_lombo_simple_desc *desc_old;
	bool change = false;

	if (!p) {
		DRM_ERROR("to_panel_lombo_simple fail\n");
		return -EINVAL;
	}
	desc_old = p->desc;

	if (!drm_mode_equal(desc->modes, desc_old->modes) ||
		desc->flag != desc_old->flag ||
		desc->dsi_fmt != desc_old->dsi_fmt ||
		desc->lanes != desc_old->lanes ||
		desc->format != desc_old->format ||
		desc->interface != desc_old->interface ||
		desc->cmd_align != desc_old->cmd_align)
		change = true;

	return change;
}
EXPORT_SYMBOL(panel_lombo_simple_config_change);

static int panel_lombo_simple_disable(struct drm_panel *panel)
{
	struct panel_lombo_simple *p = to_panel_lombo_simple(panel);

	DRM_DEBUG_KMS("[%d] p->enabled:%d\n", __LINE__, p->enabled);

	if (!p->enabled)
		return 0;

	if (p->backlight) {
		p->backlight->props.power = FB_BLANK_POWERDOWN;
		p->backlight->props.state |= BL_CORE_FBBLANK;
		backlight_update_status(p->backlight);
	}

	if (p->desc && p->desc->delay.disable)
		msleep(p->desc->delay.disable);

	p->enabled = false;

	return 0;
}

static int panel_lombo_simple_unprepare(struct drm_panel *panel)
{
	struct panel_lombo_simple *p = to_panel_lombo_simple(panel);

	DRM_DEBUG_KMS("[%d] p->prepared:%d\n", __LINE__, p->prepared);

	if (!p->prepared)
		return 0;

	if (p->reset_gpio)
		gpio_direction_output(p->reset_gpio,
			INACTIVE(p->reset_gpio_flag));

	if (p->supply)
		regulator_disable(p->supply);

	if (p->power_gpio)
		gpio_direction_output(p->power_gpio,
			INACTIVE(p->power_gpio_flag));

	if (p->desc && p->desc->delay.unprepare)
		msleep(p->desc->delay.unprepare);

	p->prepared = false;

	return 0;
}

static ssize_t __cpu_wr_init_code(struct panel_lombo_simple *panel,
	u32 *code, u32 len)
{
	u32 i = 0;
	u32 bytes = 0;
	struct lombo_vo_device *vo = to_lombo_vo_device(panel->dev);

	if (!code || !vo)
		return 0;

	while (i < len) {
		if (code[i] == 0) {
			mdelay(code[i + 1]);
			i += 2;
		} else {
			lombo_vo_write_buffer(vo, code + i + 1, code[i]);
			i += code[i] + 1;
			bytes += code[i];
		}
	}

	return bytes;
}

static ssize_t __mipi_dsi_dcs_write_array(struct panel_lombo_simple *panel,
	u8 *code, u32 len)
{
#ifdef CONFIG_DRM_LOMBO_MIPI_DSI
	u32 i = 0;
	struct mipi_dsi_device *dsi = to_mipi_dsi_device(panel->dev);

	if (!code || !dsi)
		return 0;

	while (i < len) {
		if (code[i] == 0) {
			mdelay(code[i + 1]);
			i += 2;
		} else {
			mipi_dsi_dcs_write_buffer(dsi,
				(const void *)&code[i + 1], code[i]);
			i += code[i] + 1;
		}
	}

	return i;
#else
	return 0;
#endif
}

static struct i2c_client *panel_lombo_simple_get_i2c_client(
	const unsigned short i2c_addr)
{
	uint32_t i;
	struct i2c_client *client = NULL, **clients = NULL;

	for (i = 0; i < g_client_cnt; i++) {
		client = g_clients[i];

		if (client && (client->addr == i2c_addr))
			return client;
	}

	client = i2c_new_dummy(g_clients[0]->adapter, i2c_addr);
	if (!client) {
		DRM_ERROR("dummy client for addr[%d] fail\n", i2c_addr);
		return NULL;
	}
	g_client_cnt++;

	clients = kzalloc(sizeof(client) * (g_client_cnt + 1), GFP_KERNEL);
	if (!clients) {
		DRM_ERROR("remalloc for clients fail\n");
		i2c_unregister_device(client);
		g_client_cnt--;
		return NULL;
	}

	for (i = 0; i < (g_client_cnt - 1); i++)
		clients[i] = g_clients[i];
	clients[i] = client;

	kfree(g_clients);
	g_clients = clients;
	DRM_DEBUG("added new client[%d] &0x%x\n", g_client_cnt, client->addr);

	return client;
}

static s32 __i2c_wr_init_code(u8 *reg, u32 len)
{
	u32 ret = 0, i = 0, j = 0, buf_len = 64, cmd_len = 0;
	int cnt;
	char *buf = kzalloc(buf_len, GFP_KERNEL);

	DRM_DEBUG("%s,%d\n", __func__, __LINE__);
	if (!buf) {
		DRM_ERROR("alloc for i2c cmd fail\n");
		return -ENOMEM;
	}

	while (i < len) {
		if (reg[i] == 0) {
			mdelay(reg[i + 1]);
			i += reg[i] + 2;
		} else {
			struct i2c_client *client =
			panel_lombo_simple_get_i2c_client(reg[i + 1] >> 1);

			if (reg[i] > buf_len) {
				kfree(buf);
				buf = kzalloc(reg[i], GFP_KERNEL);
				if (!buf) {
					DRM_ERROR("realloc for i2c fail\n");
					return -ENOMEM;
				}
			}

			if (client) {
				cmd_len = reg[i] - 1;
				buf[0] = reg[i + 2]; /* reg addr */

				j = 0;
				while (j < (reg[i] - 2)) { /* reg value */
					buf[j + 1] = reg[i + 3 + j];
					j++;
				}

				cnt = i2c_master_send(client, buf, cmd_len);
				if (cnt != cmd_len) {
					DRM_ERROR("i2c send fail%d\n", cnt);
					ret = cnt;
					goto out;
				}
			} else {
				DRM_ERROR("null i2c_client @%x\n",
					reg[i + 1] >> 1);
				ret = -EINVAL;
				goto out;
			}
			i += reg[i] + 1;
		}
	}
out:
	kfree(buf);
	return 0;
}

static int panel_lombo_simple_prepare(struct drm_panel *panel)
{
	struct panel_lombo_simple *p = to_panel_lombo_simple(panel);
	int err;

	DRM_DEBUG_KMS("[%d] p->prepared:%d\n", __LINE__, p->prepared);

	if (p->prepared)
		return 0;
	p->prepared = true;

	if (p->supply) {
		err = regulator_enable(p->supply);
		if (err < 0) {
			DRM_ERROR("failed to enable supply\n");
			return err;
		}
	}

	if (p->power_gpio)
		gpio_direction_output(p->power_gpio,
			ACTIVE(p->power_gpio_flag));

	if (p->sel_gpio)
		gpio_direction_output(p->sel_gpio,
			ACTIVE(p->sel_gpio_flag));

	if (p->reset_gpio) {
		gpio_direction_output(p->reset_gpio,
			INACTIVE(p->reset_gpio_flag));
		msleep(p->desc->delay.reset);
		gpio_direction_output(p->reset_gpio,
			ACTIVE(p->reset_gpio_flag));
		msleep(p->desc->delay.reset);
		gpio_direction_output(p->reset_gpio,
			INACTIVE(p->reset_gpio_flag));
		msleep(p->desc->delay.reset);
	} else {
		DRM_DEBUG_KMS("use gpadc for panel reset temporarily\n");
		lombo_write_ainx_output(3, 1);
		mdelay(p->desc->delay.reset);
		lombo_write_ainx_output(3, 0);
		mdelay(p->desc->delay.reset);
		lombo_write_ainx_output(3, 1);
		mdelay(p->desc->delay.reset);
	}

	if (p->desc && p->desc->delay.prepare)
		msleep(p->desc->delay.prepare);

	if (p->desc->init_panel == PANEL_SIMPLE_DSI &&
		p->init_code8 && p->init_code_num) {
		__mipi_dsi_dcs_write_array(p,
			p->init_code8, p->init_code_num);
	} else if (p->desc->init_panel == PANEL_SIMPLE_I2C &&
		p->init_code8 && p->init_code_num) {
		if (g_clients && g_client_cnt > 0)
			__i2c_wr_init_code(p->init_code8, p->init_code_num);
	} else if (p->desc->init_panel == PANEL_SIMPLE_CPU &&
		p->init_code32 && p->init_code_num) {
		__cpu_wr_init_code(p,
			p->init_code32, p->init_code_num);
	}

	return 0;
}

static int panel_lombo_simple_enable(struct drm_panel *panel)
{
	struct panel_lombo_simple *p = to_panel_lombo_simple(panel);

	DRM_DEBUG_KMS("[%d] p->enabled:%d\n", __LINE__, p->enabled);

	if (p->enabled)
		return 0;
	p->enabled = true;

	if (p->desc && p->desc->delay.enable)
		msleep(p->desc->delay.enable);

	if (p->backlight) {
		p->backlight->props.state &= ~BL_CORE_FBBLANK;
		p->backlight->props.power = FB_BLANK_UNBLANK;
		backlight_update_status(p->backlight);
	}

	return 0;
}

static int panel_lombo_simple_get_fixed_modes(
	struct panel_lombo_simple *panel)
{
	struct drm_connector *connector = panel->base.connector;
	struct drm_device *drm = panel->base.drm;
	struct drm_display_mode *mode;
	unsigned int i, num = 0;

	if (!panel->desc)
		return 0;

	for (i = 0; i < panel->desc->num_modes; i++) {
		const struct drm_display_mode *m = panel->desc->modes + i;

		mode = drm_mode_duplicate(drm, m);
		if (!mode) {
			DRM_ERROR("failed to add mode %ux%u@%u\n",
				m->hdisplay, m->vdisplay, m->vrefresh);
			continue;
		}

		drm_mode_set_name(mode);
		drm_mode_probed_add(connector, mode);
		num++;
	}

	connector->display_info.bpc = panel->desc->bpc;
	connector->display_info.width_mm = panel->desc->size.width;
	connector->display_info.height_mm = panel->desc->size.height;

	return num;
}

static int panel_lombo_simple_get_modes(struct drm_panel *panel)
{
	struct panel_lombo_simple *p = to_panel_lombo_simple(panel);
	int num = 0;

	/* add hard-coded panel modes */
	num += panel_lombo_simple_get_fixed_modes(p);

	return num;
}

static int panel_lombo_simple_get_timings(struct drm_panel *panel,
				    unsigned int num_timings,
				    struct display_timing *timings)
{
	DRM_DEBUG_KMS("%s,%d\n", __func__, __LINE__);
	return 0;
}

static int panel_lombo_simple_get_mode_private(struct drm_panel *panel,
		const struct drm_display_mode *mode,
		void **private)
{
	struct panel_lombo_simple *panel_simple = to_panel_lombo_simple(panel);
	struct panel_lombo_simple_desc *simple_desc = panel_simple->desc;
	struct drm_display_mode *modes = simple_desc->modes;
	struct drm_display_mode *pmode;
	int num_modes = simple_desc->num_modes;
	int i;

	DRM_DEBUG_KMS("%s,%d\n", __func__, __LINE__);
	for (i = 0; i < num_modes; i++) {
		pmode = modes + i;
		if (pmode->hdisplay == mode->hdisplay &&
			pmode->vdisplay == mode->vdisplay &&
			pmode->vrefresh == mode->vrefresh) {
			if (pmode->private)
				*private = (void **)pmode->private;
			break;
		}
	}

	return 0;
}

static const struct drm_panel_funcs panel_lombo_simple_funcs = {
	.disable = panel_lombo_simple_disable,
	.unprepare = panel_lombo_simple_unprepare,
	.prepare = panel_lombo_simple_prepare,
	.enable = panel_lombo_simple_enable,
	.get_modes = panel_lombo_simple_get_modes,
	.get_timings = panel_lombo_simple_get_timings,
	.get_mode_private = panel_lombo_simple_get_mode_private,
};

static int fill_tcon_rgb_if_info(struct panel_lombo_simple_desc *desc,
						struct device_node *np)
{
	u32 pol[5];

	/* pol: vsync,hsync,de,pixdata,syncclk */
	if (!of_property_read_u32_array(np, "polarity", pol, 5)) {
		desc->flag |= pol[0] ? LOMBO_VO_MODE_PVSYNC : 0;
		desc->flag |= pol[1] ? LOMBO_VO_MODE_PHSYNC : 0;
		desc->flag |= pol[2] ? LOMBO_VO_MODE_PDATA : 0;
		desc->flag |= pol[4] ? LOMBO_VO_MODE_PCLOCK : 0;
	}

	DRM_DEBUG("vsync_pol: %d\n", pol[0]);
	DRM_DEBUG("hsync_pol: %d\n", pol[1]);
	DRM_DEBUG("de_pol: %d\n", pol[2]);
	DRM_DEBUG("clk_pol: %d\n", pol[4]);

	return 0;
}

static int fill_tcon_cpu_if_info(struct panel_lombo_simple_desc *desc,
						struct device_node *np)
{
	int val;

	desc->flag |= of_property_read_bool(np, "has_vsync_cmd") ?
		LOMBO_VO_MODE_VSYNC_CMD : 0;
	desc->flag |= of_property_read_bool(np, "has_hsync_cmd") ?
		LOMBO_VO_MODE_HSYNC_CMD : 0;
	if (!of_property_read_u32(np, "cmd_align", &val))
		desc->cmd_align = val;
	if (!of_property_read_u32(np, "te_pol", &val))
		desc->flag |= val ? LOMBO_VO_MODE_POS_TE : 0;

	DRM_DEBUG("has_vsync_cmd: %s\n", desc->flag &
		LOMBO_VO_MODE_VSYNC_CMD ? "yes" : "no");
	DRM_DEBUG("has_hsync_cmd: %s\n", desc->flag &
		LOMBO_VO_MODE_HSYNC_CMD ? "yes" : "no");
	DRM_DEBUG("cmd_align: %d\n", desc->cmd_align);
	DRM_DEBUG("te_pol: %d\n", desc->flag & LOMBO_VO_MODE_POS_TE ? 1 : 0);

	return 0;
}

static int fill_tcon_lvds_if_info(struct panel_lombo_simple_desc *desc,
						struct device_node *np)
{
	int val;
	int ret;
	const char *mapping;

	ret = of_property_read_string(np, "data-mapping", &mapping);
	if (ret < 0) {
		DRM_ERROR("%pOF: invalid or missing %s DT property\n",
			np, "data-mapping");
		return -ENODEV;
	}
	if (!strcmp(mapping, "jeida-18")) {
		desc->flag |= LOMBO_VO_MODE_JEIDA;
		desc->format = LOMBO_VO_FMT_RGB666;
	} else if (!strcmp(mapping, "jeida-24")) {
		desc->flag |= LOMBO_VO_MODE_JEIDA;
		desc->format = LOMBO_VO_FMT_RGB888;
	} else if (!strcmp(mapping, "vesa-24")) {
		desc->flag &= ~LOMBO_VO_MODE_JEIDA;
		desc->format = LOMBO_VO_FMT_RGB888;
	} else {
		DRM_ERROR("%pOF: invalid or missing %s DT property\n",
			np, "data-mapping");
		return -EINVAL;
	}

	if (!of_property_read_u32(np, "data-mirror", &val))
		desc->flag |= val ? LOMBO_VO_MODE_DATA_LSB_TO_MSB : 0;

	DRM_DEBUG("is_ctrl_invert: %s\n", desc->flag &
		LOMBO_VO_MODE_DATA_LSB_TO_MSB ? "yes" : "no");
	DRM_DEBUG("mode: %s\n", desc->flag &
		LOMBO_VO_MODE_JEIDA ? "jeida" : "vesa");
	DRM_DEBUG("lvds format: %d\n", (int)desc->format);

	return 0;
}

static int fill_tcon_mipi_dsi_if_info(struct panel_lombo_simple_desc *desc,
						struct device_node *np)
{
	u32 val;

	if (!of_property_read_u32(np, "lanes", &val))
		desc->lanes = val;
	if (!of_property_read_u32(np, "pixel_fmt", &val))
		desc->dsi_fmt = val;
	if (!of_property_read_u32(np, "mode_flags", &val))
		desc->flag = val;

	DRM_DEBUG("lanes: %d\n", desc->lanes);
	DRM_DEBUG("pixel_fmt: %d\n", desc->dsi_fmt);
	DRM_DEBUG("mode_flags: %lu\n", desc->flag);

	return 0;
}

static int fill_panel_mode_info(struct panel_lombo_simple_desc *desc,
	struct device_node *np)
{
	u32 val = 0, values[9] = { 0 };

	if (of_property_read_bool(np, "interlaced"))
		desc->modes->flags |= DRM_MODE_FLAG_INTERLACE;
	if (!of_property_read_u32(np, "dual_link", &val))
		desc->modes->flags |= val ? DRM_MODE_FLAG_DBLCLK : 0;
	if (!of_property_read_u32_array(np, "timing", values, 9)) {
		desc->modes->clock = values[0] / 1000;
		desc->modes->hdisplay = values[4];
		desc->modes->hsync_start = values[1] + values[4];
		desc->modes->hsync_end = values[1] +	values[2] + values[4];
		desc->modes->htotal = values[1] + values[2] +
				      values[3] + values[4];

		desc->modes->vdisplay = values[8];
		desc->modes->vsync_start = values[5] + values[8];
		desc->modes->vsync_end = values[5] +	values[6] + values[8];
		desc->modes->vtotal = values[5] + values[6] +
				       values[7] + values[8];

		val = desc->modes->htotal * desc->modes->vtotal;
		desc->modes->vrefresh = values[0] / val;
	}
	DRM_DEBUG("clock: %d\n", desc->modes->clock);
	DRM_DEBUG("hdisplay: %d\n", desc->modes->hdisplay);
	DRM_DEBUG("hsync_start: %d\n", desc->modes->hsync_start);
	DRM_DEBUG("hsync_end: %d\n", desc->modes->hsync_end);
	DRM_DEBUG("htotal: %d\n", desc->modes->htotal);
	DRM_DEBUG("vdisplay: %d\n", desc->modes->vdisplay);
	DRM_DEBUG("vsync_start: %d\n", desc->modes->vsync_start);
	DRM_DEBUG("vsync_end: %d\n", desc->modes->vsync_end);
	DRM_DEBUG("vtotal: %d\n", desc->modes->vtotal);
	DRM_DEBUG("vrefresh: %d\n", desc->modes->vrefresh);
	DRM_DEBUG("interlace: %s\n", desc->modes->flags &
		DRM_MODE_FLAG_INTERLACE ? "yes" : "no");
	DRM_DEBUG("is_daul_link: %s\n", desc->modes->flags &
		DRM_MODE_FLAG_DBLCLK ? "yes" : "no");

	if (of_property_read_bool(np, "is_preferred_mode"))
		desc->modes->type |= DRM_MODE_TYPE_PREFERRED;
	DRM_DEBUG("is_preferred_mode: %s\n", desc->modes->type &
		DRM_MODE_TYPE_PREFERRED ? "yes" : "no");

	return 0;
}

static void parse_delay_time(struct panel_lombo_simple_desc *desc,
	struct device_node *np)
{
	u32 val[5];

	if (!of_property_read_u32_array(np, "delay", val, 5)) {
		desc->delay.reset = val[0];
		desc->delay.prepare = val[1];
		desc->delay.enable = val[2];
		desc->delay.disable = val[3];
		desc->delay.unprepare = val[4];
	}
	DRM_DEBUG("delay_reset: %d\n", desc->delay.reset);
	DRM_DEBUG("delay_enable: %d\n", desc->delay.enable);
	DRM_DEBUG("delay_disable: %d\n", desc->delay.disable);
	DRM_DEBUG("delay_prepare: %d\n", desc->delay.prepare);
	DRM_DEBUG("delay_unprepare: %d\n", desc->delay.unprepare);
}

static void parse_panel_physical_size(struct panel_lombo_simple_desc *desc,
	struct device_node *np)
{
	u32 val[2];

	if (!of_property_read_u32_array(np, "size-mm", val, 2)) {
		desc->size.width = val[0];
		desc->size.height = val[1];
	}
}

static int fill_vo_dev_info(struct panel_lombo_simple_desc *desc,
	struct device_node *np)
{
	u32 values[2];

	if (!of_property_read_u32_array(np, "vo_format", values, 2)) {
		desc->interface = (lombo_vo_dev_if_t)values[0];
		desc->format = (enum lombo_vo_pixel_format)values[1];
	}

	switch (desc->interface) {
	case LOMBO_VO_DEV_PRGB:
	case LOMBO_VO_DEV_SRGB_RGB:
	case LOMBO_VO_DEV_SRGB_DUMMY_RGB:
	case LOMBO_VO_DEV_SRGB_RGB_DUMMY:
		fill_tcon_rgb_if_info(desc, np);
		break;
	case LOMBO_VO_DEV_CPU_18BIT_RGB666:
	case LOMBO_VO_DEV_CPU_16BIT_RGB888:
	case LOMBO_VO_DEV_CPU_16BIT_RGB666:
	case LOMBO_VO_DEV_CPU_16BIT_RGB565:
	case LOMBO_VO_DEV_CPU_9BIT_RGB666:
	case LOMBO_VO_DEV_CPU_8BIT_RGB666:
	case LOMBO_VO_DEV_CPU_8BIT_RGB565:
	case LOMBO_VO_DEV_CPU_8BIT_RGB888:
		fill_tcon_cpu_if_info(desc, np);
		break;
	case LOMBO_VO_DEV_LVDS:
		fill_tcon_lvds_if_info(desc, np);
		break;
	case LOMBO_VO_DEV_MIPI_DSI_VIDEO:
	case LOMBO_VO_DEV_MIPI_DSI_CMD:
		fill_tcon_mipi_dsi_if_info(desc, np);
	default:
		break;
	}

	fill_panel_mode_info(desc, np);
	parse_delay_time(desc, np);
	parse_panel_physical_size(desc, np);

	return 0;
}

static int __parse_init_code32(struct device *dev, u32 **code, u32 *num)
{
	const char *prop = "init_code32";
	int cnt;
	u32 *val;

	cnt = of_property_count_u32_elems(dev->of_node, prop);
	if (cnt <= 0)
		return -ENODEV;

	val = devm_kzalloc(dev, 4 * cnt, GFP_KERNEL);
	if (!val)
		return -ENOMEM;

	if (of_property_read_u32_array(dev->of_node, prop, val, cnt))
		return -ENODEV;

	*code = val;
	*num = cnt;

	return 0;
}

static int __parse_init_code8(struct device *dev, u8 **code, u32 *num)
{
	const char *prop = "init_code";
	int cnt;
	u8 *val;

	cnt = of_property_count_u8_elems(dev->of_node, prop);
	if (cnt <= 0)
		return -ENODEV;

	val = devm_kzalloc(dev, cnt, GFP_KERNEL);
	if (!val)
		return -ENOMEM;

	if (of_property_read_u8_array(dev->of_node, prop, val, cnt))
		return -ENODEV;

	*code = val;
	*num = cnt;

	return 0;
}

static int panel_simple_init_panel_host(struct panel_lombo_simple *p)
{
	struct device *dev = p->dev;
	const char *str = NULL;

	if (!of_property_read_string(dev->of_node, "trans_if", &str)) {
		struct panel_lombo_simple_desc *desc = p->desc;

		if (!strcmp(str, "dsi")) {
			desc->init_panel = PANEL_SIMPLE_DSI;
			__parse_init_code8(dev, &p->init_code8,
				&p->init_code_num);
		} else if (!strcmp(str, "cpu")) {
			desc->init_panel = PANEL_SIMPLE_CPU;
			__parse_init_code32(dev, &p->init_code32,
				&p->init_code_num);
		} else if (!strcmp(str, "i2c")) {
			desc->init_panel = PANEL_SIMPLE_I2C;
			__parse_init_code8(dev, &p->init_code8,
				&p->init_code_num);
		} else {
			DRM_INFO("unspport trans_if=%s\n", str);
		}
	}

	return 0;
}

static struct panel_lombo_simple_desc *panel_lombo_simple_probe(
	struct device *dev)
{
	struct panel_lombo_simple *panel;
	struct device_node *np = dev->of_node, *np_bl;
	enum of_gpio_flags flags;
	int err;
	const struct of_device_id *id = of_device_get_match_data(dev);
	struct panel_lombo_simple_desc *desc_simple, *desc =
		id ? (struct panel_lombo_simple_desc *)id->data : NULL;

	panel = devm_kzalloc(dev, sizeof(*panel), GFP_KERNEL);
	if (!panel)
		return NULL;

	panel->reset_gpio =
		of_get_named_gpio_flags(np, "reset_gpio", 0, &flags);
	panel->reset_gpio_flag = flags;
	DRM_DEBUG_DRIVER("reset_gpio %d, flags %d\n", panel->reset_gpio, flags);
	if (!gpio_is_valid(panel->reset_gpio)) {
		DRM_DEBUG_DRIVER("reset_gpio %d invalid\n", panel->reset_gpio);
		panel->reset_gpio = 0;
	} else {
		err = devm_gpio_request(dev, panel->reset_gpio, "reset_gpio");
		if (err < 0) {
			DRM_ERROR("power_gpio %d request failed\n",
				panel->reset_gpio);
			panel->reset_gpio = 0;
		}
	}

	panel->power_gpio =
		of_get_named_gpio_flags(np, "power_gpio", 0, &flags);
	panel->power_gpio_flag = flags;
	DRM_DEBUG_DRIVER("power_gpio %d, flags %d\n", panel->power_gpio, flags);
	if (!gpio_is_valid(panel->power_gpio)) {
		DRM_DEBUG_DRIVER("power_gpio %d invalid\n", panel->power_gpio);
		panel->power_gpio = 0;
	} else {
		err = devm_gpio_request(dev, panel->power_gpio, "power_gpio");
		if (err < 0) {
			DRM_ERROR("power_gpio %d request failed\n",
				panel->power_gpio);
			panel->power_gpio = 0;
		}
	}

	panel->sel_gpio =
		of_get_named_gpio_flags(np, "sel_gpio", 0, &flags);
	panel->sel_gpio_flag = flags;
	DRM_DEBUG_DRIVER("sel_gpio %d, flags %d\n", panel->sel_gpio, flags);
	if (!gpio_is_valid(panel->sel_gpio)) {
		DRM_DEBUG_DRIVER("sel_gpio %d invalid\n", panel->sel_gpio);
		panel->sel_gpio = 0;
	} else {
		err = devm_gpio_request(dev, panel->sel_gpio, "sel_gpio");
		if (err < 0) {
			DRM_ERROR("sel_gpio %d request failed\n",
				panel->sel_gpio);
			panel->sel_gpio = 0;
		}
	}

	np_bl = of_parse_phandle(np, "backlight", 0);
	if (np_bl) {
		panel->backlight = of_find_backlight_by_node(np_bl);
		of_node_put(np_bl);
		if (!panel->backlight)
			DRM_ERROR("no find backlight device\n");
	}

	if (!desc) {
		panel->desc = devm_kzalloc(dev,
			sizeof(*panel->desc), GFP_KERNEL);
		if (!panel->desc)
			return NULL;
		desc_simple = panel->desc;
		desc_simple->modes = devm_kzalloc(dev,
			sizeof(struct drm_display_mode), GFP_KERNEL);
		if (!desc_simple->modes)
			return NULL;
		fill_vo_dev_info(desc_simple, np);
		desc_simple->num_modes = 1;
	} else {
		desc_simple = desc;
	}

	panel->enabled = false;
	panel->prepared = false;
	panel->dev = dev;
	panel->desc = desc_simple;

	drm_panel_init(&panel->base);
	panel->base.dev = dev;
	panel->base.funcs = &panel_lombo_simple_funcs;

	err = drm_panel_add(&panel->base);
	if (err < 0)
		goto err_out;
	panel_simple_init_panel_host(panel);
	dev_set_drvdata(dev, panel);

	return desc_simple;
err_out:
	put_device(&panel->backlight->dev);
	return NULL;
}

static int panel_lombo_simple_vo_probe(struct lombo_vo_device *vo)
{
	struct device *dev = &vo->dev;
	struct panel_lombo_simple_desc *desc;

	desc = panel_lombo_simple_probe(dev);
	if (!desc) {
		DRM_ERROR("panel_lombo_simple_probe err\n");
		return -EINVAL;
	}

	vo->interface = desc->interface;
	vo->format = desc->format;
	vo->mode_flags = desc->flag;

	return lombo_vo_attach(vo);
}

static int panel_lombo_simple_vo_remove(struct lombo_vo_device *vo)
{
	struct panel_lombo_simple *panel = dev_get_drvdata(&vo->dev);

	lombo_vo_detach(vo);
	drm_panel_detach(&panel->base);
	drm_panel_remove(&panel->base);

	panel_lombo_simple_disable(&panel->base);

	if (panel->backlight)
		put_device(&panel->backlight->dev);

	return 0;
}

static void panel_lombo_simple_vo_shutdown(struct lombo_vo_device *vo)
{
	struct panel_lombo_simple *panel = dev_get_drvdata(&vo->dev);

	panel_lombo_simple_disable(&panel->base);
}

static const struct of_device_id panel_lombo_simple_vo_of_match[] = {
	{
		.compatible = "lombo,panel_simple",
	}, {
		/* sentinel */
	}
};
MODULE_DEVICE_TABLE(of, panel_lombo_simple_vo_of_match);

static struct lombo_vo_driver panel_lombo_simple_vo_driver = {
	.driver = {
		.name = "panel-lombo-simple-vo",
		.of_match_table = panel_lombo_simple_vo_of_match,
	},
	.probe = panel_lombo_simple_vo_probe,
	.remove = panel_lombo_simple_vo_remove,
	.shutdown = panel_lombo_simple_vo_shutdown,
};

static int panel_lombo_simple_dsi_probe(struct mipi_dsi_device *dsi)
{
	struct device *dev = &dsi->dev;
	struct panel_lombo_simple_desc *desc;

	desc = panel_lombo_simple_probe(dev);
	if (!desc) {
		DRM_ERROR("panel_lombo_simple_probe err\n");
		return -EINVAL;
	}

	dsi->lanes = desc->lanes;
	dsi->format = desc->dsi_fmt;
	dsi->mode_flags = desc->flag;

	return mipi_dsi_attach(dsi);
}

static int panel_lombo_simple_dsi_remove(struct mipi_dsi_device *dsi)
{
	struct panel_lombo_simple *panel = dev_get_drvdata(&dsi->dev);

	mipi_dsi_detach(dsi);
	drm_panel_detach(&panel->base);
	drm_panel_remove(&panel->base);

	panel_lombo_simple_disable(&panel->base);

	if (panel->backlight)
		put_device(&panel->backlight->dev);

	return 0;
}

static void panel_lombo_simple_dsi_shutdown(struct mipi_dsi_device *dsi)
{
	struct panel_lombo_simple *panel = dev_get_drvdata(&dsi->dev);

	panel_lombo_simple_disable(&panel->base);
}

static const struct of_device_id panel_lombo_simple_dsi_of_match[] = {
	{
		.compatible = "lombo,panel_simple",
	}, {
		/* sentinel */
	}
};
MODULE_DEVICE_TABLE(of, panel_lombo_simple_dsi_of_match);

static struct mipi_dsi_driver panel_lombo_simple_dsi_driver = {
	.driver = {
		.name = "panel-lombo-simple-dsi",
		.of_match_table = panel_lombo_simple_dsi_of_match,
	},
	.probe = panel_lombo_simple_dsi_probe,
	.remove = panel_lombo_simple_dsi_remove,
	.shutdown = panel_lombo_simple_dsi_shutdown,
};

static int panel_lombo_simple_i2c_probe(struct i2c_client *client,
	const struct i2c_device_id *id)
{
	struct i2c_client **clients;

	if (!client) {
		DRM_ERROR("%d: null i2c_client\n", __LINE__);
		return -EINVAL;
	}

	clients = kzalloc(sizeof(client), GFP_KERNEL);
	if (!clients) {
		DRM_ERROR("alloc for clients fail\n");
		return -ENOMEM;
	}

	g_clients = clients;
	g_client_cnt++;
	*clients = client;

	DRM_DEBUG("attached panel_lombo_simple into i2c adapter success\n");

	return 0;
}

static int panel_lombo_simple_i2c_remove(struct i2c_client *client)
{
	struct i2c_client *client_simple = NULL;

	for (; g_client_cnt > 1; g_client_cnt--) {
		client_simple = g_clients[g_client_cnt - 1];
		DRM_ERROR("unregister client[%d] address 0x%02x\n",
		g_client_cnt - 1, client_simple->addr);
		i2c_unregister_device(client_simple);
	}
	kfree(g_clients);

	DRM_DEBUG("detached panel_lombo_simple from i2c adapter success\n");

	return 0;
}

static const struct i2c_device_id panel_lombo_simple_i2c_id[] = {
	{ "panel_simple_i2c", 0 },
	{ },
};

static const struct of_device_id panel_lombo_simple_i2c_match_types[] = {
	{
		.compatible = "lombo,panel_simple_i2c",
	}, {}
};

struct i2c_driver panel_lombo_simple_i2c_driver = {
	.driver = {
		.name = "panel-lombo-simple-i2c",
		.owner = THIS_MODULE,
		.of_match_table =
			of_match_ptr(panel_lombo_simple_i2c_match_types),
	},
	.id_table = panel_lombo_simple_i2c_id,
	.probe = panel_lombo_simple_i2c_probe,
	.remove = panel_lombo_simple_i2c_remove,
	.command = NULL,
};

static int __init panel_lombo_simple_init(void)
{
	int err;

	err = lombo_vo_driver_register(&panel_lombo_simple_vo_driver);
	if (err < 0)
		return err;

	if (IS_ENABLED(CONFIG_DRM_MIPI_DSI)) {
		err = mipi_dsi_driver_register(&panel_lombo_simple_dsi_driver);
		if (err < 0)
			return err;
	}

	if (i2c_add_driver(&panel_lombo_simple_i2c_driver)) {
		DRM_ERROR("failed to register panel i2c driver\n");
		return -ENOENT;
	}

	return 0;
}
module_init(panel_lombo_simple_init);

static void __exit panel_lombo_simple_exit(void)
{
	i2c_del_driver(&panel_lombo_simple_i2c_driver);
	if (IS_ENABLED(CONFIG_DRM_MIPI_DSI))
		mipi_dsi_driver_unregister(&panel_lombo_simple_dsi_driver);

	lombo_vo_driver_unregister(&panel_lombo_simple_vo_driver);
}
module_exit(panel_lombo_simple_exit);

MODULE_AUTHOR("lomboswer <lomboswer@lombotech.com>");
MODULE_DESCRIPTION("SIMPLE Panel Driver");
MODULE_LICENSE("GPL and additional rights");
